/**   
 *  Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 * @Title: ActionBindInstaller.java 
 * @Package binder.support 
 * @Description: TODO
 * @author dailey_dai  
 * @date Apr 6, 2012
 * @version V1.0   
 */
package binder.support;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;

import binder.SuperActionsBinder;
import binder.model.ActionBean;
import binder.model.annotation.Action;
import binder.model.annotation.ActionBeanFor;
import binder.model.annotation.Bind;
import binder.model.annotation.Bind2;
import binder.model.annotation.BindFor;
import binder.model.annotation.BindFor2;
import binder.model.annotation.Listener;

/**
 * Parse annotation {@link Action} and {@link Listener}, and initial action
 * binder use {@link SuperActionsBinder}
 * 
 * @author dailey_dai
 */
public class ActionBindInstaller
{
	private Object sourceBean;// source instance which contains component need
								// to bind control

	public static ActionBindInstaller build(Object sourceBean)
	{
		return new ActionBindInstaller(sourceBean);
	}

	ActionBindInstaller(Object sourceBean)
	{
		this.sourceBean = sourceBean;
	}

	/**
	 * install all {@link Bind} or {@link Bind2} annotation to component
	 * @return ActionBindInstaller
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	public ActionBindInstaller install()
		throws IOException, ClassNotFoundException, IllegalArgumentException,
		IllegalAccessException
	{
		Class<?> sourceClzz = sourceBean.getClass();
		Field[] fields = sourceClzz.getDeclaredFields();
		for (Field field : fields)
		{
			BindFor bindFor = field.getAnnotation(BindFor.class);
			BindFor2 bindFor2 = field.getAnnotation(BindFor2.class);
			Bind bind = field.getAnnotation(Bind.class);
			Bind2 bind2 = field.getAnnotation(Bind2.class);
			if (bindFor == null && bind == null && bindFor2 == null && bind2 == null)
				continue;
			field.setAccessible(true);
			Object component = field.get(sourceBean);
			if (bindFor != null)
				bindActions(
					component, bindFor.value());
			else if (bind != null)
				bindActions(
					component, bind);

			if (bindFor2 != null)
				bindActions(
					component, bindFor2.value());
			else if (bind2 != null)
				bindActions(
					component, bind2);
		}
		return this;
	}

	/**
	 * @param field
	 *            need bind control
	 * @param bindFor
	 *            Annotation for bind action and listener
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	private void bindActions(Object component, Bind... binds)
		throws IOException, ClassNotFoundException
	{
		for (Bind bind : binds)
		{
			bindAction(
				component, bind.action(), bind.listen(), BindType.BIND);
		}
	}

	private void bindActions(Object component, Bind2... binds)
		throws IOException, ClassNotFoundException
	{
		for (Bind2 bind : binds)
		{
			bindAction(
				component, bind.action(), bind.listen(), BindType.BIND2);
		}
	}

	private void bindAction(Object component, Action action, Listener listen, BindType bindType)
		throws IOException, ClassNotFoundException
	{
		ActionObject actionObj = new ActionObject(action);
		ListenerObject listenerObj = new ListenerObject(listen);
		if (ActionBean.class == actionObj.targetClass)
		{
			Logger.getLogger(
				this.getClass().getName()).log(
				Level.WARNING,
				"Faild to bind component[" + component + "] to action["
					+ actionObj.getFullActionMethod()
					+ "].\nIt may be caused by invalid action bean type[" + actionObj.targetClass
					+ "].");
			return;
		}
		String eventProperty = bindType == BindType.BIND ? null : "";
		SuperActionsBinder superBinder = new SuperActionsBinder(actionObj.targetClass);
		superBinder.bindAction(
			component, listenerObj.listenerClass, listenerObj.listenerMethod,
			actionObj.getShortActionMethod(), eventProperty);
		;
	}

	enum BindType {
		BIND, BIND2
	}

	/**
	 * wrap for annotation {@link Action}
	 * 
	 * @author dailey_dai
	 * 
	 */
	class ActionObject
	{
		private Class<?> targetClass;

		private String targetMethod;

		ActionObject(Action action)
		{
			targetMethod = action.value();
			targetClass = getActionClass(action);
		}

		private String getShortActionMethod()
		{
			String method = targetMethod;
			int index = method.lastIndexOf(".");
			if (index != -1)
			{
				method = method.substring(index + 1);
			}
			return method;
		}

		private String getFullActionMethod()
		{
			String method = targetMethod;
			int index = method.lastIndexOf(".");
			if (index == -1)
			{
				method = targetClass.getName() + "." + targetMethod;
			}
			return method;
		}

		private Class<?> getActionClass(Action action)
		{
			Class<?> actionClass = action.type();
			if (ActionBean.class == actionClass)
			{
				ActionBeanFor beanFor = sourceBean.getClass().getAnnotation(
					ActionBeanFor.class);
				Class<?>[] beanClasses = beanFor == null ? null : beanFor.value();
				actionClass = ActionClassSearcher.build(
					targetMethod).search(
					beanClasses);

			}
			return actionClass;
		}
	}

	/**
	 * wrap for annotation {@link Listener}
	 * 
	 * @author dailey_dai
	 * 
	 */
	class ListenerObject
	{
		private Class<?> listenerClass;

		private String listenerMethod;

		ListenerObject(Listener listener)
		{
			listenerClass = listener.value();
			listenerMethod = getListenerMethod(listener.method());
		}

		private String getListenerMethod(String method)
		{
			return Listener.EMPTY_METHOD.equals(method) ? null : method;
		}
	}

}
